(*  Title:      HOL/TPTP/TPTP_Parser/tptp_problem_name.ML
    Author:     Nik Sultana, Cambridge University Computer Laboratory

Scans a TPTP problem name. Naming convention is described
http://www.cs.miami.edu/~tptp/TPTP/TR/TPTPTR.shtml#Problem and Axiomatization Naming
*)

signature TPTP_PROBLEM_NAME =
sig
  datatype suffix =
      Problem of
        ((*version*)int *
        (*size parameter*)int option) *
        (*extension*)string
    | Axiom of
        (*specialisation*)int *
        (*extension*)string

  type tptp_problem_name =
    {prob_domain : string,
     prob_number : int,
     prob_form : TPTP_Syntax.language,
     suffix : suffix}

  datatype problem_name =
      Standard of tptp_problem_name
    | Nonstandard of string

  exception TPTP_PROBLEM_NAME of string

  val parse_problem_name : string -> problem_name
  val mangle_problem_name : problem_name -> string
end

structure TPTP_Problem_Name: TPTP_PROBLEM_NAME =
struct

(*some basic tokens*)
val numerics = map Char.chr (48 upto 57) (*0..9*)
val alphabetics =
  map Char.chr (65 upto 90) @ (*A..Z*)
  map Char.chr (97 upto 122)  (*a..z*)
(*TPTP formula forms*)
val forms = [#"^", #"_", #"=", #"+", #"-"]

(*lift a list of characters into a scanner combinator matching any one of the
characters in that list.*)
fun lift l =
  (map (Char.toString #> ($$)) l, Scan.fail)
  |-> fold (fn x => fn y => x || y)

(*combinators for parsing letters and numbers*)
val alpha = lift alphabetics
val numer = lift numerics

datatype suffix =
    Problem of
      ((*version*)int *
       (*size parameter*)int option) *
      (*extension*)string
  | Axiom of
      (*specialisation*)int *
      (*extension*)string

val to_int = Int.fromString #> the
val rm_ending = Scan.this_string "rm"
val ax_ending =
  ((numer >> to_int) --|
   $$ "." -- (Scan.this_string "eq" || Scan.this_string "ax" || rm_ending))
  >> Axiom
val prob_ending = $$ "p" || $$ "g" || rm_ending
val prob_suffix =
  ((numer >> to_int) --
   Scan.option ($$ "." |-- numer ^^ numer ^^ numer >> to_int) --| $$ "."
   -- prob_ending)
  >> Problem

type tptp_problem_name =
  {prob_domain : string,
   prob_number : int,
   prob_form : TPTP_Syntax.language,
   suffix : suffix}

datatype problem_name =
    Standard of tptp_problem_name
  | Nonstandard of string

exception TPTP_PROBLEM_NAME of string

fun parse_problem_name str' : problem_name =
  let
    val str = Symbol.explode str'
    (*NOTE there's an ambiguity in the spec: there's no way of knowing if a
    file ending in "rm" used to be "ax" or "p". Here we default to "p".*)
    val (parsed_name, rest) =
      Scan.finite Symbol.stopper
      (((alpha ^^ alpha ^^ alpha) --
       (numer ^^ numer ^^ numer >> to_int) --
       lift forms -- (prob_suffix || ax_ending)) >> SOME
      || Scan.succeed NONE) str

    fun parse_form str =
      case str of
        "^" => TPTP_Syntax.THF
      | "_" => TPTP_Syntax.TFF
      | "=" => TPTP_Syntax.TFF_with_arithmetic
      | "+" => TPTP_Syntax.FOF
      | "-" => TPTP_Syntax.CNF
      | _ => raise TPTP_PROBLEM_NAME ("Unknown TPTP form: " ^ str)
  in
    if not (null rest) orelse is_none parsed_name then Nonstandard str'
    else
      let
        val (((prob_domain, prob_number), prob_form), suffix) =
          the parsed_name
      in
        Standard
          {prob_domain = prob_domain,
           prob_number = prob_number,
           prob_form = parse_form prob_form,
           suffix = suffix}
      end
  end

(*Restricts the character set used in a string*)
fun restricted_ascii only_exclude =
  let
    fun restrict x = "_" ^ Int.toString (Char.ord x)
    fun restrict_uniform x =
      if member (op =) (numerics @ alphabetics) x then Char.toString x else restrict x
    fun restrict_specific x =
      if member (op =) only_exclude x then restrict x else Char.toString x
    val restrict_ascii =
      if List.null only_exclude
      then map restrict_uniform
      else map restrict_specific
  in String.explode #> restrict_ascii #> implode end;

(*Produces an ASCII encoding of a TPTP problem-file name.*)
fun mangle_problem_name (prob : problem_name) : string =
  case prob of
      Standard tptp_prob =>
        let
          val prob_form =
            case #prob_form tptp_prob of
              TPTP_Syntax.THF => "_thf_"
            | TPTP_Syntax.TFF => "_tff_"
            | TPTP_Syntax.TFF_with_arithmetic => "_thfwa_"
            | TPTP_Syntax.FOF => "_fof_"
            | TPTP_Syntax.CNF => "_cnf_"
          val suffix =
            case #suffix tptp_prob of
              Problem ((version, size), extension) =>
                Int.toString version ^ "_" ^
                (if is_some size then Int.toString (the size) ^ "_" else "") ^
                extension
            | Axiom (specialisation, extension) =>
                Int.toString specialisation ^ "_" ^ extension
        in
          #prob_domain tptp_prob ^
          Int.toString (#prob_number tptp_prob) ^
          prob_form ^
          suffix
        end
    | Nonstandard str => restricted_ascii [] str

end
